package cn.facetoken.homer;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.nnf.nnfkitlib.NNF_FaceInfo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.annotation.Nonnull;

import cn.openbiotoken.BiometricsDetector;
import cn.openbiotoken.BiometricsFactory;
import cn.openbiotoken.BiometricsFeature;
import cn.openbiotoken.BiometricsFeatureExtractor;
import cn.openbiotoken.impl.BiometricsFeatureImpl;

import static android.graphics.PixelFormat.A_8;
import static android.graphics.PixelFormat.JPEG;
import static android.graphics.PixelFormat.LA_88;
import static android.graphics.PixelFormat.L_8;
import static android.graphics.PixelFormat.RGBA_1010102;
import static android.graphics.PixelFormat.RGBA_4444;
import static android.graphics.PixelFormat.RGBA_5551;
import static android.graphics.PixelFormat.RGBA_8888;
import static android.graphics.PixelFormat.RGBA_F16;
import static android.graphics.PixelFormat.RGBX_8888;
import static android.graphics.PixelFormat.RGB_332;
import static android.graphics.PixelFormat.RGB_565;
import static android.graphics.PixelFormat.RGB_888;
import static android.graphics.PixelFormat.TRANSLUCENT;
import static android.graphics.PixelFormat.TRANSPARENT;
import static android.graphics.PixelFormat.UNKNOWN;
import static android.graphics.PixelFormat.YCbCr_420_SP;
import static android.graphics.PixelFormat.YCbCr_422_I;
import static android.graphics.PixelFormat.YCbCr_422_SP;
import static java.lang.String.format;

public class ActivityCameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {

    public ActivityCameraPreview(@Nonnull final Context context, @Nonnull final Camera camera, @Nonnull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> biometricsFactory, @NonNull final ImageView cameraShotView, @NonNull final TextView faceCountText) {
        super(context);

        this.camera = camera;
        this.biometricsFactory = biometricsFactory;

        this.cameraShotView = cameraShotView;
        this.faceCountText = faceCountText;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        //setVisibility(View.VISIBLE);
        holder = getHolder();
        holder.setKeepScreenOn(true);
        Log.d(getClass().getSimpleName(), String.format("constructor(): Surface Holder %s was got from Surface View.", holder));
        holder.addCallback(this);
        Log.d(getClass().getSimpleName(), "constructor(): Succeeded to add callback to listen to the Surface Holder.");
        // deprecated setting, but required on Android versions prior to 3.0
        //holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        then = System.currentTimeMillis();
    }

    @NonNull
    @Nonnull
    private final Camera camera;
    @Nullable
    private final SurfaceHolder holder;
    @Nonnull
    private final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> biometricsFactory;

    @NonNull
    private final ImageView cameraShotView;
    @NonNull
    private final TextView faceCountText;

    private long then = 0L;
    private int format;

    private boolean shotFrozen;

    @Override
    public void surfaceCreated(@NonNull final SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        Log.d(getClass().getSimpleName(), "surfaceCreated(): Surface holder was passed in.");

        try {
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Setting preview display for camera.");
            camera.setPreviewDisplay(holder);
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Succeeded to set preview display for camera.");

            Log.d(getClass().getSimpleName(), "surfaceCreated(): Starting preview camera.");
            camera.startPreview();
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Succeeded to start preview for camera.");
        } catch (final IOException ignored) {
            releaseCamera();
            Log.d(getClass().getSimpleName(), "surfaceCreated(): Failed to create camera surface view", ignored);
        }
    }

    @Override
    public void surfaceChanged(@NonNull final SurfaceHolder holder, final int format, final int width, final int height) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        Log.d(getClass().getSimpleName(), "surfaceChanged(): Surface holder was got.");
        Log.d(getClass().getSimpleName(), format("surfaceChanged(): Format = %d, %s.", format, formatToString(format)));
        this.format = format;
        Log.d(getClass().getSimpleName(), format("surfaceChanged(): width = %d, height = %d.", width, height));

        if (null == holder.getSurface()) {
            // preview surface does not exist
            Log.d(getClass().getSimpleName(), "surfaceChanged(): holder.surface is null. Nothing happened...");
            return;
        }

        // stop preview before making changes
        try {
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Try to stop preview...");
            camera.stopPreview();
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Succeeded to stop preview.");
        } catch (final Exception ignored) {
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Failed to stop preview before making changes.", ignored);
            // ignore: tried to stop a non-existent preview
        }
/*
        @NonNull final Camera.Parameters parameters = camera.getParameters();
        //parameters.setPreviewSize(width, height);

        // 横竖屏镜头自动调整
        if (Configuration.ORIENTATION_LANDSCAPE!=getResources().getConfiguration().orientation) {
            parameters.set("orientation", "portrait");
            parameters.set("rotation", 90); // 镜头角度转90度（默认摄像头是横拍）
            camera.setDisplayOrientation(90); // 在2.2以上可以使用
        }
        else // 如果是横屏
        {
            parameters.set("orientation", "landscape");
            camera.setDisplayOrientation(0); // 在2.2以上可以使用
        }
        camera.setPreviewCallback(this);
        camera.setParameters(parameters);
*/

        FaceCameraActivity activity = (FaceCameraActivity)getContext();
        Log.d(getClass().getSimpleName(), "surfaceChanged(): Try to set display orientation for camera...");
        setCameraDisplayOrientation(activity, activity.getCameraId(), camera);
        Log.d(getClass().getSimpleName(), "surfaceChanged(): Succeeded to set display orientation for camera.");

        Log.d(getClass().getSimpleName(), "surfaceChanged(): Try to set preview callback for camera...");
        camera.setPreviewCallback(this);
        Log.d(getClass().getSimpleName(), "surfaceChanged(): Succeeded to set preview callback for camera.");

        // set preview size and make any resize, rotate or
        // reformatting changes here
        //camera.setDisplayOrientation(90);

        // start preview with new settings
        try {
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Try to set preview display for camera...");
            camera.setPreviewDisplay(holder);
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Succeeded to set preview display for camera.");

            Log.d(getClass().getSimpleName(), "surfaceChanged(): Try to start camera preview...");
            camera.startPreview();
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Succeeded to start camera preview.");
        } catch (final Exception ignored) {
            Log.d(getClass().getSimpleName(), "surfaceChanged(): Failed to start camera preview.", ignored);
        }

        // 必须使用来自 getSupportedPreviewSizes() 的值。请勿在 setPreviewSize() 方法中设置任意值。
        // Android 7.0（API 级别 24）及更高版本引入多窗口功能，无法再假设预览的宽高比与 Activity 相同，
        // 即便调用 setDisplayOrientation() 也是如此。根据窗口尺寸和屏幕宽高比，
        // 可能需要使用 Letterbox 布局将横向的相机预览适配到纵向布局中，或反之亦然。
    }

    @Override
    public void surfaceDestroyed(@NonNull final SurfaceHolder holder) {
        // Take care of releasing the Camera preview in your activity.
        releaseCamera();
        Log.d(getClass().getSimpleName(), "surfaceDestroyed(): Surface holder was got.");
    }

    private void releaseCamera() {
        if (null!=camera) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            //camera = null;
        }
    }

    public static String formatToString(final int format) {
        switch (format) {
            case UNKNOWN:
                return "UNKNOWN";
            case TRANSLUCENT:
                return "TRANSLUCENT";
            case TRANSPARENT:
                return "TRANSPARENT";
            case RGBA_8888:
                return "RGBA_8888";
            case RGBX_8888:
                return "RGBX_8888";
            case RGB_888:
                return "RGB_888";
            case RGB_565:
                return "RGB_565";
            case RGBA_5551:
                return "RGBA_5551";
            case RGBA_4444:
                return "RGBA_4444";
            case A_8:
                return "A_8";
            case L_8:
                return "L_8";
            case LA_88:
                return "LA_88";
            case RGB_332:
                return "RGB_332";
            case YCbCr_422_SP:
                return "YCbCr_422_SP";
            case YCbCr_420_SP:
                return "YCbCr_420_SP";
            case YCbCr_422_I:
                return "YCbCr_422_I";
            case RGBA_F16:
                return "RGBA_F16";
            case RGBA_1010102:
                return "RGBA_1010102";
            //case HSV_888:
            case 0x37:
                return "HSV_888";
            case JPEG:
                return "JPEG";
            default:
                return Integer.toString(format);
        }
    }

    private static void setCameraDisplayOrientation(@NonNull final Activity activity,
                                                   final int cameraId, @NonNull final Camera camera) {
        camera.setDisplayOrientation(calculateCameraDisplayOrientation(activity, cameraId, camera));
    }

    private static int calculateCameraDisplayOrientation(@NonNull final Activity activity,
                                                    final int cameraId, @NonNull final Camera camera) {
        @NonNull final Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        final int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): activity.windowManager.defaultDisplay.rotation = %d, degrees = %d.", 0, 0));
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): activity.windowManager.defaultDisplay.rotation = %d, degrees = %d.", 90, 90));
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): activity.windowManager.defaultDisplay.rotation = %d, degrees = %d.", 180, 180));
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): activity.windowManager.defaultDisplay.rotation = %d, degrees = %d.", 270, 270));
                break;
        }

        int result;
        if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
            Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): Front facing camera. info.orientation = %d.", info.orientation));
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): Back facing camera. info.orientation = %d.", info.orientation));
            result = (info.orientation - degrees + 360) % 360;
        }
        Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("calculateCameraDisplayOrientation(): result = %d.", result));
        return result;
    }

    @Override
    public void onPreviewFrame(final byte[] data, final Camera camera) {

        if (shotFrozen) {
            return;
        }

        long timeSpent = System.currentTimeMillis()-then;
        then = System.currentTimeMillis();
        Log.i(getClass().getSimpleName(), String.format("onPreviewFrame(): data.length = %d, time spent = %d ms.", data.length, timeSpent));

        new Thread(
                () -> {
                    @NonNull final Bitmap bitmap = convertFrameToBitmap(data);
                    if (null==bitmap) {
                        Log.w(getClass().getSimpleName(), "onPreviewFrame(): Null bitmap was converted from byte[].");
                        return;
                    }
                    Log.i(getClass().getSimpleName(), String.format("onPreviewFrame(): bitmap.size = (%d, %d).", bitmap.getWidth(), bitmap.getHeight()));

                    @NonNull final FaceCameraActivity activity = (FaceCameraActivity)getContext();
                    @NonNull final Matrix matrix = new Matrix();
                    matrix.postRotate(-calculateCameraDisplayOrientation(activity, activity.getCameraId(), camera));

                    @NonNull final Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                    Log.i(getClass().getSimpleName(), String.format("onPreviewFrame(): rotatedBitmap.size = (%d, %d).", rotatedBitmap.getWidth(), rotatedBitmap.getHeight()));
                    bitmap.recycle();

                    @NonNull final List<FaceSample> samples = MainApplication.getMainDatabase().faceSampleDao().loadAll();

                    /*
                    @NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> eyeprintFactory = new HomoSapiensEyeprintBitmapBiometricsFactory(getContext());
                    final FaceSample topSimilarEyeprintSample = matchSample(eyeprintFactory, bitmap, samples);
                    if (null!=topSimilarEyeprintSample) {
                        // call UI
                        @NonNull final PhotoRegistration<String> photo = topSimilarEyeprintSample.toPhotoRegistration();
                        Log.i(getClass().getSimpleName(), String.format("run(): Top Similar Eyeprint: id= %d, name = %s.", photo.id, photo.name));
                        return;
                    }
                    */

                    //@NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> faceFactory = new HomoSapiensFaceBitmapBiometricsFactory(getContext());
                    @NonNull final NNF_FaceInfo face = findFace(biometricsFactory, rotatedBitmap);

                    activity.runOnUiThread(
                            () -> {
                                cameraShotView.setImageBitmap(rotatedBitmap);
                                faceCountText.setText(String.format("%s %d", activity.getResources().getString(R.string.label_face_count), face.faceNum));
                            }
                    );

                    if (face.faceNum<=0) {
                        return;
                    }

                    final FaceSample topSimilarFaceSample = matchSample(biometricsFactory, rotatedBitmap, samples);

                    if (null!=topSimilarFaceSample) {
                        shotFrozen = true;
                        Log.i(getClass().getSimpleName(), String.format("onPreviewFrame(): run(): Top Similar Face Sample: id = %d, name = %s.", topSimilarFaceSample.id, topSimilarFaceSample.name));
                        activity.runOnUiThread(
                                () -> {
                                    faceCountText.setText(getResources().getString(R.string.label_similarity_matched, topSimilarFaceSample.getSimilarity()*100));
                                    activity.updateTopSimilarSample(topSimilarFaceSample);
                                }
                        );
                    }

                }
        ).start();
    }

    private NNF_FaceInfo findFace(@NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> faceFactory, @NonNull final Bitmap bitmap) {
        @NonNull final BiometricsDetector<NNF_FaceInfo> detector = faceFactory.createDetector(bitmap);
        @NonNull final NNF_FaceInfo face = detector.detect();
        Log.i(getClass().getSimpleName(), String.format("findFace(): degrees = %d, detected face count = %d, face score = %f, trace ID = %d.", 0, face.faceNum, face.faceScore, face.faceTrackID));
        return face;
    }

    @NonNull
    private Bitmap convertFrameToBitmap(@NonNull final byte[] data) {
        //camera.setOneShotPreviewCallback(null);
        //处理data
        @NonNull final Camera.Parameters params;
        try {
            params = camera.getParameters();
        } catch (RuntimeException e) {
            Log.e(getClass().getSimpleName(), "Failed to get parameters from Camera. Return null bitmap", e);
            return null;
        }

        @NonNull final Camera.Size previewSize = params.getPreviewSize();//获取尺寸,格式转换的时候要用到
        @NonNull final BitmapFactory.Options preProcessingOptions = new BitmapFactory.Options();
        preProcessingOptions.inJustDecodeBounds = true;
        @NonNull final YuvImage yuvImage = new YuvImage(
                data,
                ImageFormat.NV21,
                previewSize.width,
                previewSize.height,
                null);
        @NonNull final ByteArrayOutputStream out = new ByteArrayOutputStream();
        yuvImage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, out);// 80--JPG图片的质量[0-100],100最高
        byte[] rawImage = out.toByteArray();
        //将rawImage转换成bitmap

        @NonNull final BitmapFactory.Options decodingOptions = new BitmapFactory.Options();
        decodingOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
        /*
        switch (this.format) {
            case A_8:
                decodingOptions.inPreferredConfig = Bitmap.Config.ALPHA_8;
                break;
            case RGB_565:
                decodingOptions.inPreferredConfig = Bitmap.Config.RGB_565;
                break;
            case RGBA_4444:
                decodingOptions.inPreferredConfig = Bitmap.Config.ARGB_4444;
                break;
            case RGBA_8888:
                decodingOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
                break;
            default:
                decodingOptions.inPreferredConfig = Bitmap.Config.RGB_565;
            //case RGBA_F16:
            //    decodingOptions.inPreferredConfig = Bitmap.Config.RGBA_F16;
            //    break;
            //default:
            //    decodingOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
        }*/

        Log.i(getClass().getSimpleName(), String.format("convertFrameToBitmap(): decodingOptions.inPreferredConfig = %s.", decodingOptions.inPreferredConfig.name()));
        @NonNull final Bitmap bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, decodingOptions);
        return bitmap;
    }

    @Nullable
    private static FaceSample matchSample(@NonNull final BiometricsFactory<NNF_FaceInfo, float[], Bitmap> factory, @NonNull final Bitmap bitmap, @NonNull final List<FaceSample> samples) {
        @NonNull final BiometricsFeatureExtractor<float[]> extractor = factory.createFeatureExtractor(bitmap);
        @NonNull final BiometricsFeature<float[]> sampleFeature = extractor.extract();
        Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("matchSample(): sampleFeature.feature.length = %d.", sampleFeature.getFeature().length));
        if (sampleFeature.getFeature().length>256) {
            sampleFeature.getFeature()[0] = 1;
            @NonNull final float[] tail = Arrays.copyOfRange(sampleFeature.getFeature(), 256, sampleFeature.getFeature().length);

            int count = 0;
            for (float v : tail) {
                if (0.0F == v) {
                    count++;
                }
            }
            Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("matchSample(): count of zero in tail: %d.", count));
            if (256!=count) {
                Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("matchSample(): tail = %s.", Arrays.toString(tail)));
            }
        }

        @NonNull final List<BiometricsFeature<float[]>> library = new ArrayList<>(samples.size());
        for (FaceSample sample : samples) {
            BiometricsFeature<float[]> appFeature = new BiometricsFeatureImpl<>();
            appFeature.setFeature(sample.getFaceFeature());
            appFeature.putExtra(BiometricsFeature.EXTRA_KEY_SAMPLE_ID, sample.id);
            library.add(appFeature);
        }

        final BiometricsFeature<float[]> matched = extractor.top(sampleFeature, Collections.unmodifiableList(library), 0.6F);
        Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("matchSample(): matched = %s.", matched));

        if (null==matched) {
            final float topSimilarity = 0.0F;
            Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("matchSample(): Top Similarity = %f.", topSimilarity));
            @Nullable final FaceSample topSimilarSample = null;
            return topSimilarSample;
        } else {
            final float topSimilarity = matched.getFloatExtra(BiometricsFeature.EXTRA_KEY_SIMILARITY);
            Log.d(ActivityCameraPreview.class.getSimpleName(), String.format("matchSample(): Top Similarity = %f.", topSimilarity));
            @Nullable final FaceSample topSimilarSample = samples.stream().filter(sample -> matched.getLongExtra(BiometricsFeature.EXTRA_KEY_SAMPLE_ID) == sample.id).findFirst().orElse(null);
            topSimilarSample.setSimilarity(topSimilarity);
            return topSimilarSample;
        }
    }

    /*
    private void saveBitmap(@NonNull final Bitmap bitmap)  {
        @NonNull final File file = new File(getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), "Homer_Preview_" + System.currentTimeMillis() + ".jpeg");
        Log.i(getClass().getSimpleName(), String.format("saveBitmap(): Try to save bitmap into file: %s.", file.getAbsolutePath()));
        try {
            @NonNull final OutputStream out = new FileOutputStream(file);//创建输入流
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
            out.close();
            // 实现相册更新
            @NonNull final Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            @NonNull final Uri uri = Uri.fromFile(file);
            Log.i(getClass().getSimpleName(), String.format("saveBitmap(): Succeeded to save bitmap into URI: %s.", uri.toString()));
            intent.setData(uri);
            getContext().sendBroadcast(intent); // 更新图库
        } catch(Exception e) {
            Log.e(getClass().getSimpleName(), "saveBitmap(): Failed to save bitmap.", e);
        }
    }
    */
}
